package dev.ideas.funkyj.sequence;

import dev.ideas.funkyj.function.*;
import dev.ideas.funkyj.group.Group1;
import dev.ideas.funkyj.group.Group2;
import dev.ideas.funkyj.group.Group3;
import dev.ideas.funkyj.tuple.Single;

import java.util.*;

/**
 */
public class Sequence<X> extends _Sequence<X> {

    protected Sequence(Iterable<?> from) {super(from); }

    //
    @Override public Sequence<X> where(P<? super X> p) { return (Sequence<X>) super.where(p); }
    @Override public Sequence<X> where(P<? super X>... ps) { return (Sequence<X>) super.where(ps); }
    @Override public Sequence<X> parallel() { return (Sequence<X>) super.parallel(); }

    public static <T1, T2> Sequence2<T1, T2> from(final Iterable<T1> t1, final Iterable<T2> t2) {
        return new Sequence2<T1, T2>(t1, t2);
    }

    public static <T1, T2, T3> Sequence3<T1, T2, T3> from(final Iterable<T1> t1, final Iterable<T2> t2, final Iterable<T3> t3) {
        return new Sequence3<T1, T2, T3>(t1, t2, t3);
    }

    /*to pair*/
    public <T1, T2> Sequence2<T1, T2> to(final F<X, T1> f1, final F<X, T2> f2) {
        return new Sequence2<T1, T2>(from(this).to(f1), from(this).to(f2));
    }

    /*to trio*/
    public <T1, T2, T3> Sequence3<T1, T2, T3> to(final F<X, T1> f1, final F<X, T2> f2, final F<X, T3> f3) {
        return new Sequence3<T1, T2, T3>(from(this).to(f1), from(this).to(f2), from(this).to(f3));
    }

    /*to single*/
    public <T1> Single<T1> to(final F<Iterable<X>, T1> f) {
        return new Single<T1>() {
            @Override public T1 get() {
                return f.apply(Sequence.this);
            }
        };
    }

    /*to flat*/
    public Single<X> to(final F2<X, X, X> f) {
        return new Single<X>() {
            public X get() {
                X t1 = null;

                final Iterator<X> ti = Sequence.this.iterator();

                if (ti.hasNext()) {
                    t1 = ti.next();
                }
                while (ti.hasNext()) {
                    X t2 = ti.next();
                    t1 = f.apply(t1, t2);
                }

                return t1;
            }
        };
    }

    //
    public <T1> Sequence2<X, T1> zip(final Iterable<T1> t1s) {
        return from(this, Sequence.from(t1s));
    }

    public <T1> Sequence2<X, T1> zip(final T1... t1s) {
        return from(this, Sequence.from(t1s));
    }

    //groupBy
    public <G1> Group1<G1, X> groupBy(final F<X, G1> f1) {
        return new Group1<G1, X>(iterator(), f1);
    }

    public <G1, G2> Group2<G2, G1, X> groupBy(final F<X, G2> f2, final F<X, G1> f1) {
        return new Group2<G2, G1, X>(iterator(), f2, f1) {
            public void apply(X x) {
                final Group1<G1, X> select = select(f2.apply(x));
                final Sequence<X> select1 = select.select(f1.apply(x));
            }
        };
    }

    public <G1, G2, G3> Group3<G1, G2, G3, X> groupBy(final F<X, G1> f1, final F<X, G2> f2, final F<X, G3> f3) {
        return new Group3<G1, G2, G3, X>(iterator(), f1, f2, f3);
    }

    //aggregation support
    //todo can use Number and cast it to the inferred type? would wanna only have sumF, and not sumF, sum
    //todo aggregation methods should be lazy, and return Single
    public float sumF(final F<X, Float> f) {
        float sum = 0;
        for (X x : this) {
            sum += f.apply(x);
        }
        return sum;
    }

    public int sum(final F<X, Integer> f) {
        int sum = 0;
        for (X x : this) {
            sum += f.apply(x);
        }
        return sum;
    }

    /**unsafe*/ @SuppressWarnings({"unchecked"}) @Deprecated public float sum() {
        return sumF(identity);
    }

    /**unsafe*/ @SuppressWarnings({"unchecked"}) @Deprecated public float avg() {
        return avg(identity);
    }

    public float avg(final F<X, Float> f) {
        float avg = 0;
        int count = 0;
        for (X x : this) {
            avg += f.apply(x);
            count++;
        }
        return avg / count;
    }

    public int count() {
        int count = 0;
        //noinspection UnusedDeclaration
        for (X x : this) count++;
        return count;
    }

    /**unsafe*/ @SuppressWarnings({"unchecked"}) @Deprecated public Float min() {
        return min(identity);
    }
    public Float min(final F<X, Float> f) {
        Float min = null;
        for (X x : this) {
            final Float v = f.apply(x);
            if (min == null || v < min) {
                min = v;
            }
        }
        return min;
    }
}
